// Learn TypeScript:
//  - [Chinese] http://docs.cocos.com/creator/manual/zh/scripting/typescript.html
//  - [English] http://www.cocos2d-x.org/docs/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - [Chinese] http://docs.cocos.com/creator/manual/zh/scripting/reference/attributes.html
//  - [English] http://www.cocos2d-x.org/docs/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - [Chinese] http://docs.cocos.com/creator/manual/zh/scripting/life-cycle-callbacks.html
//  - [English] http://www.cocos2d-x.org/docs/creator/manual/en/scripting/life-cycle-callbacks.html

import Hero from "./hero";
import GameBoard from "./game/gameboard";
import {MathUtils} from "./math/MathUtils";
import {frameUpdate} from "./frame/FrameUtils";

const {ccclass, property} = cc._decorator;

enum AiState {
  IDLE,
  ATTACK_TARGET,
}

@ccclass
export default class AiScript extends cc.Component {
  // LIFE-CYCLE CALLBACKS:
  heroScript: Hero = null;

  gameBoard: GameBoard = null;

  playersBoard: cc.Node = null;

  target: cc.Node;

  private _state: AiState = AiState.IDLE;

  // aiConfigs = [
  //   {
  //     attackDuration: 2,
  //     isAcc: false,
  //     detectInterval: 2,
  //     detectRange: 100,
  //     moveDuration: 2,
  //     judgeAccInterval: 2,
  //     idleAccDuration: 2,
  //     idleAccProbability: 20,
  //     attackProbability: 30,
  //     scoreRatio: [1, 1]
  //   }
  // ];
  private _currentAiConfig;
  set currentAiConfig(value) {
    this._currentAiConfig = value;
  }

  onLoad() {
    this.heroScript = this.node.getComponent(Hero)
    this.gameBoard = this.heroScript.gameBoard;
    this.playersBoard = this.gameBoard.node.getChildByName('players')
    this.randomDirection();
    this.heroScript.node.on('reachBound', this.onHeroReachBound, this)
  }

  start() {
    this.heroScript.heroMove = true;
    this.randomDirection();
  }

  private accumulate = 1;

  private lastTimer = 0;

  onHeroReachBound() {
    const now = Date.now()
    if (now - this.lastTimer > 300) {
      this.lastTimer = now;
      this.heroScript.moveToTargetAngle(this.heroScript.node.rotation + Math.random() * 180)
    }
  }

  @frameUpdate()
  updateFixedFrame(dt) {
    if (!this.heroScript) {
      return;
    }
    switch (this._state) {
      case AiState.IDLE:
        this._updateIdle(dt)
        break;
      case AiState.ATTACK_TARGET:
        this._updateAttack(dt);
        break;
    }
  }

  private _updateIdle(dt) {
    if (this.isDetectReady(dt)) {
      this.target = this.detectEnemy();
      if (this.target) {
        return this.state = AiState.ATTACK_TARGET;
      }
    }
    if (this.needChangeDirection(dt)) {
      this.randomDirection();
    }
    if (!this.heroScript.isAccelerate) {
      //如果不在加速，判断是否需要加速
      if (this.judgeAccReady(dt)) {
        if (Math.random() * 100 > this._currentAiConfig.idleAccProbability) {
          this.heroScript.accelerate()
        }
      }
    } else {
      if (this.isIdleAccEnough(dt)) {
        this.heroScript.speedToNormal()
      }
    }
  }

  private _updateAttack(dt) {
    if (this.isAttackEnough(dt)) {
      return this.state = AiState.IDLE;
    }
    if (this.target.getComponent(Hero).immortal === true) {
      return this.state = AiState.IDLE;
    }
    this.heroScript.accelerate();
    this.moveToPoint(this.target.x, this.target.y)
  }

  set state(value: AiState) {
    switch (value) {
      case AiState.IDLE:
        this.heroScript.speedToNormal()
        break
      case AiState.ATTACK_TARGET:
        break
    }
    this._state = value;
  }

  private detectTime = 0;

  private isDetectReady(dt) {
    if (this.detectTime > this.getArrayRandomRange(this._currentAiConfig.detectInterval)) {
      this.detectTime = 0;
      return true;
    }
    this.detectTime += dt;
    return false;
  }

  private attackTime = 0;

  private isAttackEnough(dt) {
    if (this.attackTime > this.getArrayRandomRange(this._currentAiConfig.attackDuration)) {
      this.attackTime = 0;
      return true;
    }
    this.attackTime += dt;
    return false;
  }

  private moveTime = 0;

  private needChangeDirection(dt) {
    if (this.moveTime > this.getArrayRandomRange(this._currentAiConfig.moveDuration)) {
      this.moveTime = 0;
      return true;
    }
    this.moveTime += dt;
    return false;
  }

  private judgeInterval = 0;

  private judgeAccReady(dt) {
    if (this.judgeInterval > this._currentAiConfig.judgeAccInterval) {
      this.judgeInterval = 0;
      return true;
    }
    this.judgeInterval += dt;
    return false;
  }

  private idleAccDuration = 0;

  private isIdleAccEnough(dt) {
    if (this.idleAccDuration > this.getArrayRandomRange(this._currentAiConfig.idleAccDuration)) {
      this.idleAccDuration = 0;
      return true;
    }
    this.idleAccDuration += dt;
    return false;
  }

  onFindTarget() {
    this.accumulate = 0;
    if (Math.random() > 0.7) {
      this._state = AiState.ATTACK_TARGET
    } else {
      // this.state = AiState.AWAY_TARGET
    }
  }

  isTargetInGameBoard() {
    const re = this.playersBoard.children.find(value => this.target === value)
    return !!re;
  }

  randomDirection() {
    const angle = Math.floor(Math.random() * 360)
    this.heroScript.moveToTargetAngle(angle)
  }

  detectEnemy(): cc.Node {
    const len = this.playersBoard.children.length
    for (let i = 0; i < len; i++) {
      const player = this.playersBoard.children[i]
      if (player === this.heroScript.node) {
        continue
      }
      if (Math.abs(player.position.x - this.heroScript.node.x) < this._currentAiConfig.detectRange &&
        Math.abs(player.position.y - this.heroScript.node.y) < this._currentAiConfig.detectRange) {
        this.onFindTarget();
        return player;
      }
    }
    return null;
  }

  getTargetAngle(x: number, y: number) {
    const angle = Math.atan2(x - this.heroScript.node.x, y - this.heroScript.node.y);
    let targetAngle = angle * 180 / MathUtils.PI;
    if (targetAngle < 0) {
      targetAngle += 360
    }
    return targetAngle;
  }

  moveToPoint(x: number, y: number) {
    this.heroScript.moveToTargetAngle(this.getTargetAngle(x, y));
  }

  getScoreRatio() {
    return this.getArrayRandomRange(this._currentAiConfig.scoreRatio)
  }

  private getArrayRandomRange(arr: Array<number>) {
    return Math.floor(Math.random() * (arr[1] - arr[0])) + arr[0]
  }

}
